<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>响应式编程在前端领域的应用 | 被删的前端游乐场</title>
    <meta name="generator" content="VuePress 1.8.2">
    
    <meta name="description" content="Just playing around">
    
    <link rel="preload" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css" as="style"><link rel="preload" href="/front-end-playground/assets/js/app.1e2670bf.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/2.38d016d1.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/3.e3f029cb.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/35.60ec8abf.js" as="script">
    <link rel="stylesheet" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/front-end-playground/" class="home-link router-link-active"><!----> <span class="site-name">被删的前端游乐场</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link router-link-active">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link router-link-active">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav>  <ul class="sidebar-links"><li><section class="sidebar-group collapsable depth-0" style="padding-top:10px;"><div class="kitty-main" data-v-2b653b36><span class="stand" data-v-2b653b36></span> <div class="cat" data-v-2b653b36><div class="body" data-v-2b653b36></div> <div class="head" data-v-2b653b36><div class="ear" data-v-2b653b36></div> <div class="ear" data-v-2b653b36></div></div> <div class="face" data-v-2b653b36><div class="nose" data-v-2b653b36></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div></div> <div class="tail-container" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36></div></div></div></div></div></div></div></div></div></div> <p class="sidebar-heading open"><span>前端架构</span> <span class="arrow down"></span></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/front-end-playground/front-end-basic/deep-learning/module-seperate.html" class="sidebar-link">谈谈依赖和解耦</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/trace-stash.html" class="sidebar-link">大型前端项目要怎么跟踪和分析函数调用链</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/build-application.html" class="sidebar-link">前端构建大型应用</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/reactive-programing.html" aria-current="page" class="active sidebar-link">响应式编程在前端领域的应用</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-basic/deep-learning/reactive-programing.html#异步数据流" class="sidebar-link">异步数据流</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-basic/deep-learning/reactive-programing.html#响应式编程在前端领域" class="sidebar-link">响应式编程在前端领域</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-basic/deep-learning/reactive-programing.html#比较其他技术" class="sidebar-link">比较其他技术</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-basic/deep-learning/reactive-programing.html#热观察与冷观察" class="sidebar-link">热观察与冷观察</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-basic/deep-learning/reactive-programing.html#合流" class="sidebar-link">合流</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-basic/deep-learning/reactive-programing.html#其他使用方式" class="sidebar-link">其他使用方式</a></li></ul></li><li><a href="/front-end-playground/front-end-basic/deep-learning/vscode-event.html" class="sidebar-link">VSCode 源码解读：事件系统设计</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/vscode-ipc.html" class="sidebar-link">VSCode 源码解读：IPC通信机制</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/online-doc-network.html" class="sidebar-link">在线文档的网络层设计思考</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/front-end-performance-analyze.html" class="sidebar-link">补齐Web前端性能分析的工具盲点</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/monitor-and-report.html" class="sidebar-link">前端监控体系搭建</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/why-spreadsheet-app-excited.html" class="sidebar-link">在线Excel项目到底有多刺激</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/task-runner-design.html" class="sidebar-link">如何设计一个任务管理器</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/network-design-responsibility-driven-design.html" class="sidebar-link">在线文档的网络层开发思考--职责驱动设计</a></li><li><a href="/front-end-playground/front-end-basic/deep-learning/network-design-dependency-decoupling.html" class="sidebar-link">在线文档的网络层开发思考--依赖关系梳理</a></li></ul></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>前端深入理解</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>前端入门</span> <span class="arrow right"></span></p> <!----></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><p>其实在几年前因为 Angular 的原因接触过响应式编程，而这些年的一些项目经验，让我在再次回顾响应式编程的时候又有了新的理解。</p> <h1 id="什么是响应式编程"><a href="#什么是响应式编程" class="header-anchor">#</a> 什么是响应式编程</h1> <p>响应式编程基于观察者模式，是一种面向数据流和变化传播的声明式编程方式。</p> <h2 id="异步数据流"><a href="#异步数据流" class="header-anchor">#</a> 异步数据流</h2> <p>响应式编程常常用在异步数据流，通过订阅某个数据流，可以对数据进行一系列流式处理，例如过滤、计算、转换、合流等，配合函数式编程可以实现很多优秀的场景。</p> <p>除了天然异步的前端、客户端等 GUI 开发以外，响应式编程在大数据处理中也同样拥有高并发、分布式、依赖解耦等优势，在这种同步阻塞转异步的并发场景下会有较大的性能提升，淘宝业务架构就是使用响应式的架构。</p> <h2 id="响应式编程在前端领域"><a href="#响应式编程在前端领域" class="header-anchor">#</a> 响应式编程在前端领域</h2> <p>在前端领域，常见的异步编程场景包括事件处理、用户输入、HTTP 响应等。对于这类型的数据流，可以使用响应式编程的方式来进行设计。</p> <p>不少开发者基于响应式编程设计了一些工具库，包括 Rxjs、Mobx、Cycle.js 等。其中，Rxjs 提供了基于可观察对象（Observable）的 functional reactive programming 服务，Mobx 提供了基于状态管理的 transparent functional reactive programming 服务，而 Cycle.js 则是一个响应式前端框架。</p> <p>我们可以结合具体场景来介绍下使用，这里会以 Rxjs 来说明。</p> <h3 id="http-请求与重试"><a href="#http-请求与重试" class="header-anchor">#</a> HTTP 请求与重试</h3> <p>基于响应式编程，我们可以很简单地实现一个请求的获取和自动重试：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">import</span> <span class="token punctuation">{</span> ajax <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">&quot;rxjs/ajax&quot;</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token punctuation">{</span> map<span class="token punctuation">,</span> retry<span class="token punctuation">,</span> catchError <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">&quot;rxjs/operators&quot;</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> apiData <span class="token operator">=</span> <span class="token function">ajax</span><span class="token punctuation">(</span><span class="token string">&quot;/api/data&quot;</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">pipe</span><span class="token punctuation">(</span>
  <span class="token comment">// 可以在 catchError 之前使用 retry 操作符。它会订阅到原始的来源可观察对象，此处为重新发起 HTTP 请求</span>
  <span class="token function">retry</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// 失败前会重试最多 3 次</span>
  <span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">res</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>res<span class="token punctuation">.</span>response<span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">&quot;Value expected!&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token keyword">return</span> res<span class="token punctuation">.</span>response<span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
  <span class="token function">catchError</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token keyword">of</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

apiData<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  <span class="token function">next</span><span class="token punctuation">(</span><span class="token parameter">x</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">&quot;data: &quot;</span><span class="token punctuation">,</span> x<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token function">error</span><span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">&quot;errors already caught... will not run&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><h3 id="用户输入"><a href="#用户输入" class="header-anchor">#</a> 用户输入</h3> <p>对应用户的一些交互，也可以通过订阅的方式来获取需要的信息：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> observable <span class="token operator">=</span> Rx<span class="token punctuation">.</span>Observable<span class="token punctuation">.</span><span class="token function">fromEvent</span><span class="token punctuation">(</span>input<span class="token punctuation">,</span> <span class="token string">&quot;input&quot;</span><span class="token punctuation">)</span> <span class="token comment">// 监听 input 元素的 input 事件</span>
  <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span> <span class="token comment">// 一旦发生，把事件对象 e 映射成 input 元素的值</span>
  <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> value<span class="token punctuation">.</span>length <span class="token operator">&gt;=</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token comment">// 接着过滤掉值长度小于 1 的</span>
  <span class="token punctuation">.</span><span class="token function">distinctUntilChanged</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// 如果该值和过去最新的值相等，则忽略</span>
  <span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span>
    <span class="token comment">// subscribe 拿到数据</span>
    <span class="token punctuation">(</span><span class="token parameter">x</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token punctuation">(</span><span class="token parameter">err</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span>
  <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 订阅</span>
observable<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">x</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>在用户频繁交互的场景，数据的流式处理可以让我们很方便地进行节流和防抖。除此之外，模块间的调用和事件通信同样可以通过这种方式来进行处理。</p> <h2 id="比较其他技术"><a href="#比较其他技术" class="header-anchor">#</a> 比较其他技术</h2> <p>接触响应式编程这个概念的时候，大多数人都会对它产生困惑，也比较容易与 Promise、事件订阅这些设计混淆。我们来一起看看。</p> <h3 id="promise"><a href="#promise" class="header-anchor">#</a> Promise</h3> <p>Promise 相信大家也都很熟悉，在这里拿出来比较，其实更多是将 Rxjs 中的 Observable 与之比较。这两个其实很不一样：</p> <ul><li>Promise 会发生状态扭转，状态扭转不可逆；而 Observable 是无状态的，数据流可以源源不断，可用于随着时间的推移获取多个值</li> <li>Promise 在定义时就会被执行；而 Observable 只有在被订阅时才会执行</li> <li>Promise 不支持取消；而 Observable 可通过取消订阅取消正在进行的工作</li></ul> <h3 id="事件"><a href="#事件" class="header-anchor">#</a> 事件</h3> <p>同样是基于观察者模式，相信很多人都对事件和响应式编程之间的关系比较迷惑。而根据具体的设计实现，事件和响应式编程模式可以达到高度相似。</p> <p>一个比较显著的区别在于，由于响应式编程是面向数据流和变化传播的模式，意味着我们可以对数据流进行配置处理，使其在把事件传给事件处理器之前先进行转换。同样由于流式处理，响应式编程可以把包含一堆异步/事件的组合从开头到结尾用流的操作符清晰表示，而原始事件回调只能表示一堆相邻节点的关系，对于数据流动方向和过程都可以进一步掌握。</p> <p>同时，结合响应式编程的合流、缓存等能力，我们可以收获更多。</p> <h1 id="响应式编程提供了怎样的服务"><a href="#响应式编程提供了怎样的服务" class="header-anchor">#</a> 响应式编程提供了怎样的服务</h1> <p>前面说了很多，相信大家对响应式编程的概念和使用有一定的理解了。现在，我们一起来看看它还能给我们带来怎样的服务。</p> <h2 id="热观察与冷观察"><a href="#热观察与冷观察" class="header-anchor">#</a> 热观察与冷观察</h2> <p>在 Rxjs 中，有热观察和冷观察的概念。其中的区别：</p> <ul><li>Hot Observable，可以理解为现场直播，我们进场的时候只能看到即时的内容</li> <li>Cold Observable，可以理解为点播（电影），我们打开的时候会从头播放</li></ul> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">let</span> liveStreaming$ <span class="token operator">=</span> Rx<span class="token punctuation">.</span>Observable<span class="token punctuation">.</span><span class="token function">interval</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">take</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

liveStreaming$<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span>
	<span class="token parameter">data</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'subscriber from first second'</span><span class="token punctuation">)</span>
	<span class="token parameter">err</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">,</span>
	<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'completed'</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span>

<span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
	liveStreaming$<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span>
		<span class="token parameter">data</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'subscriber from 2nd second'</span><span class="token punctuation">)</span>
		<span class="token parameter">err</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">,</span>
		<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'completed'</span><span class="token punctuation">)</span>
	<span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">2000</span><span class="token punctuation">)</span>
<span class="token comment">// 事实上两个订阅者接收到的值都是 0,1,2,3,4，此处为冷观察</span>
</code></pre></div><p>Rxjs 中 Observable 默认为冷观察，而通过<code>publish()</code>和<code>connect()</code>可以将冷的 Observable 转变成热的：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">let</span> publisher$ <span class="token operator">=</span> Rx<span class="token punctuation">.</span>Observable<span class="token punctuation">.</span><span class="token function">interval</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">take</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">publish</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

publisher$<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span>
	<span class="token parameter">data</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'subscriber from first minute'</span><span class="token punctuation">,</span>data<span class="token punctuation">)</span><span class="token punctuation">,</span>
	<span class="token parameter">err</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">,</span>
	<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'completed'</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span>

<span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    publisher$<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span>
        <span class="token parameter">data</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'subscriber from 2nd minute'</span><span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token parameter">err</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'completed'</span><span class="token punctuation">)</span>
    <span class="token punctuation">)</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token number">3000</span><span class="token punctuation">)</span>

publisher$<span class="token punctuation">.</span><span class="token function">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 第一个订阅者输出的是0,1,2,3,4，而第二个输出的是3,4，此处为热观察</span>
</code></pre></div><p>热观察和冷观察根据具体的场景可能会有不同的需要，而 Observable 提供的缓存能力也能解决不少业务场景。例如，如果我们想要在拉群后，自动同步之前的聊天记录，通过冷观察就可以做到。同样的，热观察的用途也很广泛。</p> <h2 id="合流"><a href="#合流" class="header-anchor">#</a> 合流</h2> <p>流的处理大概是响应式编程中最有意思的部分了。一般来说，合流有两种方式：</p> <div class="language-bash extra-class"><pre class="language-bash"><code><span class="token comment"># 1. merge</span>
--1----2-----3--------4---
----a-----b----c---d------
           merge
--1-a--2--b--3-c---d--4---

<span class="token comment"># 2. combine</span>
--1----2-----3--------4---
----a-----b-----c--d------
         combine
----1a-2a-2b-3b-3c-3d-4d--
</code></pre></div><p>那这样的合流方式，可以具体应用到哪里呢？</p> <p>例如，merge 的合流方式可以用在群聊天、聊天室，一些多人协作的场景、公众号订阅的场景就可以通过这样的方式合流，最终按照顺序地展示出对应的操作记录。</p> <p>再举个例子，我们在 Excel 中，通过函数计算了 A1 和 B2 两个格子的相加。这种情况下，使用 combine 方式合流符合预期，那么我们可以订阅这么一个流：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">const</span> streamA1 <span class="token operator">=</span> Rx<span class="token punctuation">.</span>Observable<span class="token punctuation">.</span><span class="token function">fromEvent</span><span class="token punctuation">(</span>inputA1<span class="token punctuation">,</span> <span class="token string">&quot;input&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 监听 A1 单元格的 input 事件</span>
<span class="token keyword">const</span> streamB2 <span class="token operator">=</span> Rx<span class="token punctuation">.</span>Observable<span class="token punctuation">.</span><span class="token function">fromEvent</span><span class="token punctuation">(</span>inputB2<span class="token punctuation">,</span> <span class="token string">&quot;input&quot;</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 监听 B2 单元格的 input 事件</span>

<span class="token keyword">const</span> subscribe <span class="token operator">=</span> <span class="token function">combineLatest</span><span class="token punctuation">(</span>streamA1<span class="token punctuation">,</span> streamB2<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">valueA1<span class="token punctuation">,</span> valueB2</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
	<span class="token comment">// 从 streamA1 和 streamB2 中获取最新发出的值</span>
    <span class="token keyword">return</span> valueA1 <span class="token operator">+</span> valaueB2<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 获取函数计算结果</span>
observable<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">x</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>x<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>在一个较大型的前端应用中，通常会拆分成渲染层、数据层、网络层、其他服务等多个功能模块。虽然服务按照功能结构进行拆分了，但依然会存在服务间调用导致依赖关系复杂、事件触发和监听满天飞等情况，这种情况下，只能通过全局搜索关键字来找到上下游数据流、信息流，通过一个接一个的节点和关键字搜索才能大概理清楚某个数据来源哪里。</p> <p>那么，如果使用了响应式编程，我们可以通过各种合流的方式、订阅分流的方式，来将应用中的数据流动从头到尾串在一起。这样，我们可以很清晰地当前节点上的数据来自于哪里，是用户的操作还是来自网络请求。</p> <h2 id="其他使用方式"><a href="#其他使用方式" class="header-anchor">#</a> 其他使用方式</h2> <p>除了上面提到的一些 HTTP 请求、用户操作、事件管理等可以使用响应式编程的方式来实现，我们还可以将定时器、数组/可迭代对象变量转换为可观察序列。</p> <h3 id="timer"><a href="#timer" class="header-anchor">#</a> timer</h3> <p>也就是说，如果我们界面中有个倒计时，就可以以定时器为数据源，订阅该数据流进行响应：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// timerOne 在 0 秒时发出第一个值，然后每 1 秒发送一次</span>
<span class="token keyword">const</span> timerOne <span class="token operator">=</span> <span class="token function">timer</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token parameter">x</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
	<span class="token comment">// 触发界面更新</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>定时器结合合流的方式，我们还可以玩出更多的花样。例如，界面中有三个倒计时，我们需要在倒计时全部结束之后展示一些内容，这个时候我们就可以通过将三个倒计时 combine 合流，当三个流都处于倒计时终止的状态时，触发相应的逻辑。</p> <h3 id="数组-可迭代对象"><a href="#数组-可迭代对象" class="header-anchor">#</a> 数组/可迭代对象</h3> <p>我们可以将数组或者可迭代的对象，转换为可观察的序列。</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token keyword">var</span> array <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">;</span>

<span class="token comment">// 打印出每个项目</span>
<span class="token keyword">const</span> subscription <span class="token operator">=</span> Rx<span class="token punctuation">.</span>Observable<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>array<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span>
	<span class="token parameter">x</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'onNext: %s'</span><span class="token punctuation">,</span> x<span class="token punctuation">)</span><span class="token punctuation">,</span>
    <span class="token parameter">e</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'onError: %s'</span><span class="token punctuation">,</span> e<span class="token punctuation">)</span><span class="token punctuation">,</span>
	<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'onCompleted'</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// =&gt; onNext: 1</span>
<span class="token comment">// =&gt; onNext: 2</span>
<span class="token comment">// =&gt; onNext: 3</span>
<span class="token comment">// =&gt; onNext: 4</span>
<span class="token comment">// =&gt; onNext: 5</span>
<span class="token comment">// =&gt; onCompleted</span>
</code></pre></div><p>乍一看，似乎只是将遍历换了种写法，其实这样的能力可以用在更多的地方。例如，我们在离线编辑文档的时候，做了很多操作，这些操作在本地会用一个操作记录数组的方式缓存下来。当应用检测到网络状态恢复的时候，可以将这样的操作组转换为有序的一个个操作同步到远程服务器。（当然，更好的设计应该是支持批量有序地上传操作到服务器）</p> <h1 id="结束语"><a href="#结束语" class="header-anchor">#</a> 结束语</h1> <p>对响应式编程的介绍暂告一段落。</p> <p>可见对于很多复杂程度较低的前端应用来说，其实入门成本比较高。但在一些复杂应用的场景，合理地使用响应式编程，可以有效地降低各个模块间的依赖，更加容易地进行整体数据流动管理和维护。</p> <p>这么有意思的东西，你要不要来试试看？</p></div> <!----> <footer class="page-edit"><div class="edit-link"><a href="https://github.com/godbasin/front-end-playground/edit/sourcecode/docs/front-end-basic/deep-learning/reactive-programing.md" target="_blank" rel="noopener noreferrer">帮阿猪改善此页面！</a> <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></div> <!----> <blockquote>部分文章中使用了一些网站的截图，如果涉及侵权，请告诉我删一下谢谢~</blockquote> <div style="margin-top:30px;"><div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-sm-0 el-col-md-2 el-col-lg-4" style="padding-left:10px;padding-right:10px;display:block;"><div style="width:1px;height:1px;"></div></div> <div class="el-col el-col-24 el-col-sm-24 el-col-md-18 el-col-lg-16" style="padding-left:10px;padding-right:10px;"><div class="el-card box-card is-always-shadow"><div class="el-card__header"><div class="clearfix"><span>温馨提示喵</span></div></div><div class="el-card__body"> <div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="el-image"><div class="image-slot"><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/loading.gif" style="width:100%;"></div><!----></div></div> <div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="copyright-text"><div>本文版权归作者所有，欢迎转载，但未经作者同意必须保留此段声明，且在文章页面明显位置给出原文连接，否则保留追究法律责任的权利。</div> <div>出处：被删的前端游乐场</div> <div>作者：<a href="https://github.com/godbasin" target="_blank">被删</a></div></div></div></div></div></div></div></div></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/front-end-playground/front-end-basic/deep-learning/build-application.html" class="prev">
          前端构建大型应用
        </a></span> <span class="next"><a href="/front-end-playground/front-end-basic/deep-learning/vscode-event.html">
          VSCode 源码解读：事件系统设计
        </a>
        →
      </span></p></div>  <div class="gitalk-container theme-default-content"><div id="gitalk-container" class="content"></div></div></main> <div id="kitty-container"><span><div role="tooltip" id="el-popover-8105" aria-hidden="true" class="el-popover el-popper" style="width:undefinedpx;display:none;"><!----><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/2code2.jpg" class="image"> <div class="text">牡羊猪的猫粮罐</div> </div><span class="el-popover__reference-wrapper"><div id="kitty" style="background:url(https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/kitty1.svg);"></div></span></span> <div class="el-dialog__wrapper" style="display:none;"><div role="dialog" aria-modal="true" aria-label="牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）" class="el-dialog" style="margin-top:15vh;"><div class="el-dialog__header"><span class="el-dialog__title">牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）</span><button type="button" aria-label="Close" class="el-dialog__headerbtn"><i class="el-dialog__close el-icon el-icon-close"></i></button></div><!----><!----></div></div></div></div><div class="global-ui"></div></div>
    <script src="/front-end-playground/assets/js/app.1e2670bf.js" defer></script><script src="/front-end-playground/assets/js/2.38d016d1.js" defer></script><script src="/front-end-playground/assets/js/3.e3f029cb.js" defer></script><script src="/front-end-playground/assets/js/35.60ec8abf.js" defer></script>
  </body>
</html>
